package smallChanges.lineNumberMapping;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LdiffLineMappings
{
    private LineMapping mapping=new LineMapping();
    private String ldiffExecutable=
        "/Users/jspacco/Colgate/projects/smallChanges/scam2009/scripts/ldiff.pl -o ext";
    private String options=" ";
    private Set<String> optionMap=new HashSet<String>();
    
    public LdiffLineMappings() {}
    
    public LineMapping getLineMapping() {
        return mapping;
    }
    
    public String getConfigName() {
        //return "LDiff" +options.replace(" +", "");
        return "LDiff" +options.trim();
    }
    
    public LdiffLineMappings(String ldiffExecutable) {
        this.ldiffExecutable=ldiffExecutable;
    }
    
    public void setLdiffExecutable(String ldiffExecutable) {
        this.ldiffExecutable=ldiffExecutable;
    }
    
    public void addOption(String opt) {
        options+=" "+opt+" ";
        optionMap.add(opt);
    }
    
    public long runLdiff(String leftFilename, String rightFilename)
    throws IOException, InterruptedException
    {
        String cmd=ldiffExecutable+" "+
            options+" " +
            leftFilename+" " +
            rightFilename;
        long start=System.currentTimeMillis();
        final Process p=Runtime.getRuntime().exec(cmd);
        ReadThread in=new ReadThread(p.getInputStream());
        ReadThread err=new ReadThread(p.getErrorStream());
        in.start();
        err.start();
        int r=p.waitFor();
        // Not sure why but ldiff returns 1 on a successful run
        long time=System.currentTimeMillis()-start;
        if (r!=1) {
            throw new IOException("ldiff subprocess terminated abnormally with status " +r+
                    err.getOutput());
        }
        in.join();
        err.join();
        read(new StringReader(in.getOutput()));
        return time;
    }
    
    private static class ReadThread extends Thread
    {
        private InputStream in;
        private String output;
        public ReadThread(InputStream in) {
            this.in=in;
        }
        public String getOutput() {
            return output;
        }
        public void run() {
            Scanner scanner=new Scanner(in);
            StringBuffer buf=new StringBuffer();
            while (scanner.hasNextLine()) {
                buf.append(scanner.nextLine());
                buf.append("\n");
            }
            output=buf.toString();
        }
    }
    
    public static LdiffLineMappings readFromReader(Reader r)
    throws IOException
    {
        LdiffLineMappings result=new LdiffLineMappings();
        result.read(r);
        return result;
    }
    
    private void read(Reader r)
    throws IOException
    {
        BufferedReader reader=new BufferedReader(r);
        while (true) {
            String line=reader.readLine();
            if (line==null) break;
            Pattern p=Pattern.compile("(\\d+),(\\d+)([acud])(\\d+),(\\d+).*");
            Matcher m=p.matcher(line);
            if (m.matches()) {
                int lhsStart=Integer.parseInt(m.group(1));
                int lhsEnd=Integer.parseInt(m.group(2));
                String changeType=m.group(3);
                int rhsStart=Integer.parseInt(m.group(4));
                int rhsEnd=Integer.parseInt(m.group(5));
                if (changeType.equals("u")) {
                    int rhs=rhsStart;
                    for (int lhs=lhsStart; lhs<=lhsEnd; lhs++) {
                        mapping.addUnchanged(lhs, rhs);
                        rhs++;
                    }
                } else if (changeType.equals("c")) {
                    int rhs=rhsStart;
                    for (int lhs=lhsStart; lhs<=lhsEnd; lhs++) {
                        mapping.addChanged(lhs, rhs);
                        rhs++;
                    }
                } else if (changeType.equals("c")) {
                    for (int lhs=lhsStart; lhs<=lhsEnd; lhs++) {
                        mapping.addDeleted(lhs);
                    }
                }
            }
        }
    }
}
